#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>


//1


//int main()
//{
//	int  a = 20;
//	//变量创建的本质其实是在内存中申请空间
//	//向内存申请4个字节的空间，用来存放20这个数值
//	//这4个字节，每个字节都有编号(地址)
//	//变量的名字仅仅是给程序员看的，编译器不看名字，编译器是通过地址找内存单元的
//	&a;//&--取地址操作符,拿到变量a的地址  取第一个字节的地址，其它的顺藤摸瓜可得
//	printf("%p\n", &a);//%p打印地址
//	int* pa = &a;//指针变量pa存放地址(指针)的，放一个地址的量(4或8)个字节
//	return 0;
//}


//2


//int main()
//{
//	int a = 20;
//	int* pa = &a;
//	*pa = 200;//* -- 解引用操作符(间接访问操作符)
//	printf("%d\n", a);
//	printf("%d\n", *pa);
//	return 0;
//}


//3


//int main()
//{
//	int a = 10;
//	printf("%p\n", &a);//&a取的是4个字节的a变量的第一个字节的地址
//	int* p = &a;
//	//指针变量是用来存放地址的，一个地址的存放需要多大空间，那么指针变量的大小就是多大
//	printf("%zd\n", sizeof(p));//X64环境下指针变量的大小为8byte,因为有64根地址线，一个地址64bit；X86是32位环境，指针变量的大小为4byte
//	printf("%p\n", p);
//	return 0;
//}


//4 指针类型没有决定指针变量的大小(指针什么类型变量都是放一个字节的一个标准地址的大小量),但决定了它解引用时访问多大的权限


//int main()
//{
//	printf("%zd\n", sizeof(char*));
//	printf("%zd\n", sizeof(short*));
//	printf("%zd\n", sizeof(int*));
//	printf("%zd\n", sizeof(float*));
//	printf("%zd\n", sizeof(double*));
//	return 0;
//}


//5


////(1)
//int main()
//{
//	int a = 0x11223344;
//	int* pa = &a;//pa里面存的值是&a地址，但pa存值的变量申请的空间的第一个字节(x64环境下一共8个字节)的地址(&pa)是与&a是不一样的
//	*pa = 0;//但此时pa的值是&a地址的值
//	return 0;
//}
////(2)
//int main()
//{
//	int a = 0x11223344;
//	char* pa = &a;
//	*pa = 0;
//	return 0;
//}


//6


//int main()
//{
//	int a = 0x11223344;
//	int* pa = &a;
//	char* pc = &a;
//	printf("&a=%p\n", &a);
//	printf("pa=%p\n", pa);
//	printf("pc=%p\n", pc);
//
//	printf("&a+1=%p\n", &a+1);
//	printf("pa+1=%p\n", pa+1);
//	printf("pc+1=%p\n", pc+1);
//	return 0;
//}


//指针就是放地址的变量
//7 void* -- 无具体类型的指针(泛型指针),可以用来接收任意类型的地址,这种指针接收地址之后地址就少了类型性质了，viod*类型的指针不能进行指针的+-整数和解引用的运算，变量创建出来仅用来放


//int main()
//{
//	int a = 10;//&a是整型性质地址
//	int* pa = &a;//无警告
//	char* pc = &a;//整型性质地址放到字符指针变量中改成了字符性质地址，有警告
//	char ch = 'w';
//	void* pv1 = &a;//无警告
//	void* pv2 = &ch;//无警告
//	//*void pv1 = 200;//err
//	//pv1 + 1;//err
//	return 0;
//}
//
////虽然因为不知道类型控几个字节而不能解引用和加减数值，但它传值传纯地址也是可以的，传对应的指针变量里有控字节能力，也能对那块地址里的值进行修改
// int main()
// {
//     int a = 0;
//     void* p = &a;
//     int* q = p;
//     *q = 20;
//     printf("a = %d\n", a);
//     return 0;
// }


//8 常属性
//const修饰的变量叫常变量
//这个被修饰的变量本质上还是变量，只是不能被修改


//int main()
//{
//	const int num = 100;//这个变量具有了常量的属性
//	//num = 200;//err
//	printf("%d\n", num);
//	return 0;
//}


//9 


//int main()
//{
//	int arr[10] = { 0 };
//
//	//int n = 10;
//	//int arr[n];//err
//	//不支持变长数组，数组大小是需要常量、常量表达式来指定的，不能是变量
//	//const int n = 10;
//	//int arr[n];//const修饰后的int n 还是变量
//	return 0;
//}


// 10


//int main()
//{
//	const int n = 10;
//	//n = 20;
//	int* p = &n;
//	*p = 200;
//	printf("%d\n", n);
//	return 0;
//}


//11


//int main()
//{
//	int a = 10;
//	printf("%p\n", &a);
//	*&a = 20;
//	printf("%d\n", a);
//	return 0;
//}


//12


//int main()
//{
//	int n = 10;
//	int m = 100;
//	int* p = &n;
//	*p = 20;//变量n改了
//	p = &m;//变量p也改了
//	//p代替&m完成，效果一样的那种
//	char* q = &m;
//	//q代替&m完成，但效果变成了一个字节
//	return 0;
//}


// 13限制*p限制其对后的4地址性质，没有使此性质消失，而是限制了用此性质访问的这个操作,地址解引用针对对象被锁


//int main()
//{
//	int n = 10;
//	int m = 100;
//	int const* p = &n;//或const int* p = &n;
//	//const 修饰指针变量
//	//放在*左边，限制的是指针指向的内容，也就是不能通过指针变量来修改它所指向的内容
//	//但是指针变量本身是可以改变的
//	*p = 20;//err
//  *(p + 1) = 20;//err//就不能用此指针来解引用改值了
//  printf(*(pa + 1));//访问功能还是可以的，就少了个改值
//  int* pa = p + 1;
//  *pa = 20;
//	p = &m;
//	int* d = p + 1;
//	char* e = p + 1;
//	&n + 1;
//	printf("%p\n", p);
//	printf("%p\n", d);
//	printf("%p\n", e);
//
//	int* const q = &n;
//	//指针变量里放的地址是不会变了，但放地址里的值还是可变的
//	//const 放在*右边，限制的是指针变量本身，指针不能改变它的指向地址
//	//但是可以通过指针变量修改它所指向的内容
//	*q = 20;
//	q = &m;//err
//	int const* const r = &n;
//	r = &m;//err
//	*r = 200;//err
//	return 0;
//}


//14 指针+-整数


//int main()
//{//数组在内存中是连续存放的
//	int arr[10] = { 1,2,3,4,9,6,7,8,9,10 };
//	int count = (sizeof(arr) / sizeof arr[0]);
//	//它会搜索类型整个数组类型就写数组名
//
//	int i = 0;
//	int* p = &arr[0];
//
//	for (i = 0;i < count;i++)
//	{
//		printf("%d ", *p);
//		p++;
////or	//printf("%d ", arr[i]);
////err	printf("%d ", *p + i+1,2);//一个%d搜索一个逗号区表达式结果
////or	//printf("%d ", *(p + i));
//	}
//	printf("\n");
//	int* q = &arr[9];
//	for (i = 0;i < count;i++)
//	{
//		/*printf("%d ", *(q - i));*/
//		printf("%d ", *q);
//		q--;
//	}
//		return 0;
//}


//15 指针-指针
//指针-指针的绝对值是指针和指针之间元素的个数
//指针-指针，计算的前提条件是两个指针指向的是同一个空间
//指针相减量会自动转化为单位元个数
//等于到达个数(后头不算的)
//相减两头有一头一步是不走的
//指针+指针没意义


//int main()
//{//下标小的地址小
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	printf("%d\n", &arr[9] - &arr[0]);//步数变得不以数字为单位的，它们在所属数字中相对位置一样，它们之间的距离也能用数字长度来算，它们之间相关的也能用数字关系来表示，就是相当于，36个字节相当于等于9个数字长度，恰好它自己又会转好
//	printf("%d\n", &arr[0] - &arr[9]);//它们之间转的单位元个数可以直接看所角标  //并不会直接转成绝对值
//	char ch[6] = { 0 };
//	printf("%d\n", &ch[5] - &arr[0]);//无意义
//
//	return 0;
//}
//
// int main()
// {
//     //不同类型变量的地址(整形a与字符c)也可算他们之间的地址差量，但转成单位个数时因为不同变量占字节量大小不一样，单位个数算时是统一以一方变量(被减数)大小来算的
//     int a = 0;
//     int* pa = &a;
//     char b = 0;
//     printf("pa - &b = %d\n", pa - &b);
//     printf("&a - &b = %d\n", &a - &b);
//     //算出来的单位个数并不是他们之间的元素个数，数组里是同类型，相减因为它们之间确实是存在这些单位元元素，算出来的个数即之间的到达元素个数(不包的是右头的)，有意义
//     int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//     printf("&arr[9] - arr = %d\n", &arr[9] - arr);
//     //void*类型的指针地址不能与整数相加减，指针与指针相加减时void*指针不能做被减数
//     int c = 0;
//     void* pc = &c;
//     char d = 0;
//     void* pd = &d;
//     printf("%d\n", pc - pd);//void*指针变量做被减数了//err
//     printf("&c - pd = %d\n", &c - pd);//可算
//     return 0;
// }
//
////用strlen函数求字符串长度
//int main()
//{//字符数组里放的是字符串，用字符合成的字符字符串字符串末尾没有\0；这里是正常的有\0的
//	char arr[6] = "abcdef";//{ "abcdef" }//err
//	//字符数组初始化时，要么""全部字符堆里面，要么{}里面一个个''引的字符
//	size_t len = strlen(arr);//用的库函数
//	printf("%zd\n", len);
//	//字符串本身自己的6个字符加上末尾加上来的'\0'共7个字符,如果是因为\0而多一个位置放不下的，不会报错，它会把\0前的就全放好，\0的就不放了，此时的字符串末尾没有\0，所以strlen搜索时会一直往后找记
//	int lon = sizeof(arr);//算的是此数组类型的字节大小，它是字符类型的数组，开辟多少个元素空间该类型的大小就多少个字节，但不能用此算数组里字符的个数，因为开辟多少个元素空间不一定就等于放了多少个元素，可能没放满
//	//而且sizeof算\0一个字节，而strlen算字符串里字符个数时不算\0
//	//所以不能用sizeof算字符串里字符个数，要用strlen
//	printf("%d\n", lon);
//	return 0;
//}
//
////自己写一个函数，求字符串的长度
//size_t text_strlen(char* p)
//{//size_t 是一种无符号整型
//	size_t count = 0;
//	//I can't 
//	while (*p != '\0')//or while (*p)
//	{
//		count++;
//		// I make mistake --> p + 1;
//		p = p + 1;//p++
//	}
//	return count;
//}
////改进函数
// #include<assert.h>
//size_t text_strlen(const char* p)
//{//             //更健壮了一点，至少不能从第一个字符修改来捣蛋了
//	assert(p!=NULL);//检测指针p是否有效
//	//*p = 's';//err
//	char* q = p;
//	while (*p!='\0')//  \  后面跟1~3个八进制数字，表示ASSIA表中八进制排的字符，搜索其ASSIA码十进制值也就是0
//		//所以可改进为 --> while(*p)
//	{
//		p++;
//	}//I make mistake --> return (&p - &q);
//	return p - q;
//}
//
//int main()
//{//     下标0~6，7个元素
//	char arr[7] = "abcdef";
//	size_t lon = text_strlen(arr);//传数组名传的是数组首元素地址 arr = &arr[0]
//	printf("%zd\n", lon);
//	return 0;
//}


//16 指针的关系运算


//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int* p = &arr[0];
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	while (p < &arr[sz])
//	{
//		printf("%d ", *p);
//		p++;
//	}
//	return 0;
//}


//17 p指向申请之外的空间并且访问它，p就是野指针，造成内存的非法访问


////指针未初始化
//int main()
//{
//	//一个局部变量不初始化的话，它的值是随机的
//	//int a;//它不会给你默认初始化0的
//	//printf("%d\n", a);
//
//	int arr[1];//它这边就给你乱放值了
//	printf("%d\n", arr[1]);
//
//	//int* p;//p是局部变量，但是没有初始化，其值是随机值的，如果将p中存放的值当作地址，解引用操作符就会形成非法访问
//	//*p = 20;//p就是野指针
//	//p指向申请之外的空间并且访问它
//	return 0;
//}
//
////指针越界访问
//int main()
//{//共10个元素
//	int arr[10] = { 0 };
//	int* p = &arr[0];
//	int i = 0;
//	//for循环有明确循环次数，while循环没有明确
//	for (i = 0;i <= 10;i++)//循环11次
//	{
//		*p = i;//i=10时赋给解引用&arr[10]的*p，p就成了野指针
//		p++;
//	}
//	return 0;
//}
//
////指针指向的空间释放
//int* text()
//{
//	int n = 100;//局部变量，出来后就销毁了，这块内存空间就还给操作系统了
//	return &n;//要避免返回局部变量的地址
//}
//int main()
//{
//	int* p = text();
//	printf("%d\n", *p);//*p,p成野指针了
//	return 0;
//}


//17


//int main()
//{
//	int a;
//	int* p = &a;//给一个明确的地址
//	*p = 10;
//	printf("%d\n", *p);
//
//	int* p2 = NULL;
//	//*p2 = 200;//err
//	if (p2 != NULL)
//	{
//		*p2 = 200;
//	}
//	
//	return 0;
//}


//18 assert断言


///*#define NDEBUG*/ //全部调试完对一些还存在的assert语句的一键全关语句
////assert的开关，定义了相当于关了，是一键把全部assert关了，比去注释一条条的assert语句方便、比删许多的if语句方便；选择关也是检查完调试完调整完后干的事，因为此语句是帮助程序员调试的，release产品版本此语句作用一定是做完了任务的一定最后关了的，如果在此版本中遇到会认为是忘关的直接忽略掉，因为是语句就会有执行时间
//#include<assert.h>
//int main()
//{
//	int b;
//	int* q = &b;
//	printf("hehe\n");
//	//int* p = &10;//err
//	//常量10不是变量没创建内存空间，是变量就有开辟内存空间就有地址
//	int* a = NULL;
//	assert(a != NULL);//判断是否这样，真进假退对的是程序，一个隐形保障
//	printf("hehe\n");
//	return 0;
//}


//19 


//int main()
//{
//	int a = 10;
//	int* p = &a;
//	*p;//没问题
//	10;
//	int* q = p;
//	return 0;
//}


//20 传址调用
//函数形参局部的,各自各的，要影响到外面有一定的隔阂(用return、赋全局变量)
//传地址直接改外部，形参直接等于外部的，相当于直接直对外部的
//变量跨越范围互通的存在形式


//void exchange(int* x, int* y)
//{
//	int z = 0;
//	z = *x;
//	*x = *y;
//	*y = z;
//}
//int main()
//{
//	int a = 0;
//	int b = 0;
//	scanf("%d %d", &a, &b);
//	printf("交换前，a=%d,b=%d\n", a, b);
//	exchange(&a, &b);//传址调用
//	printf("交换后，a=%d,b=%d\n", a, b);
//	return 0;
//}